/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit;

import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getCovereds;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.EList;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.uml2.uml.ExecutionSpecification;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.OccurrenceSpecification;
import org.eclipse.uml2.uml.UMLPackage;

import com.cea.papyrus.core.editor.ModelManager;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.edit.policy.DeletionEditPolicy;
import com.cea.papyrus.diagram.sequence.sedi.figures.AnchorContributor;
import com.cea.papyrus.diagram.sequence.sedi.figures.AnchoringFigure;
import com.cea.papyrus.diagram.sequence.sedi.figures.EOFigure;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * Edit part for {@link ExecutionSpecification}.
 * 
 * @author Andreas Limyr
 * @author frankd@ifi.uio.no
 */
public class EOEditPart extends AnchoringEditPart<ExecutionSpecification> {

	/**
	 * The {@link Diagram} that contains the {@link EOEditPart}
	 */
	private Diagram diagram; 
	
	/**
	 * Constructor
	 * @param view
	 * @param modelManager
	 */
    public EOEditPart(GraphElement view, ModelManager modelManager) {
        super(view, modelManager);
        
        // Get diagram on EditPart creation instead of calling getDiagram
        // dynamically
        // Dynamic call is problematic during deletion (from Model or Diagram)
        // because the graphical container may be null.
        diagram = LookForElement.getDiagram(getGraphNode());
    }

    /**
     * Create the {@link ExecutionSpecification} figure
     */
    protected IFigure createFigure() {
        EOFigure figure = new EOFigure();
        figure.setMessageAccess(this);
        return figure;
    }

    /**
     * Add specific policies to the {@link EOEditPart} to handle
     * functionalities like deletion
     */
    protected void createEditPolicies() {
        super.createEditPolicies();
        removeEditPolicy(EditPolicy.DIRECT_EDIT_ROLE);
        
        // Add edit policy to handle deletion requests (replace SeDiSubpartEditPolicy)
		installEditPolicy(
				EditPolicy.COMPONENT_ROLE,
		        new DeletionEditPolicy());
    }

    private Set<Lifeline> contributingTo = new HashSet<Lifeline>();

    @Override
    protected void refreshVisuals() {
        super.refreshVisuals();
       
        // Update anchor contribution to covered lifelines
        final Set<Lifeline> obsolete = new HashSet<Lifeline>(contributingTo);
        final ExecutionSpecification execSpec = getUmlElement();
        if (getUmlElement().getStart() == null) {
        	// The start OccurenceSpecification could not be found
        	// the element is probably deleted 
        	// (ExecutionSpecification deletion deletes Start and Finish
        	return;
        }
        for (Lifeline lifeline : getCovereds(execSpec.getStart())) { // TODO Is it
            // correct to use
            // getStart()?
            obsolete.remove(lifeline);

            if (!contributingTo.contains(lifeline)) {
                AnchoringFigure lifelineFigure = getLifelineFigure(lifeline);
                if (lifelineFigure != null) {
                lifelineFigure.addAnchorContributor((AnchorContributor) getFigure());
                contributingTo.add(lifeline);
                }
            }
        }
        for (Lifeline lifeline : obsolete) {
            AnchoringFigure lifelineFigure = getLifelineFigure(lifeline);
            if (lifelineFigure != null) {
            lifelineFigure.removeAnchorContributor((AnchorContributor) getFigure());
            contributingTo.remove(lifeline);
            }
        }
    
//        getParent().refresh();
    }

    private void removeAllAnchorContributions() {
        for (Lifeline lifeline : contributingTo) {
            AnchoringFigure lifelineFigure = getLifelineFigure(lifeline);
            if (lifelineFigure != null)
                lifelineFigure.removeAnchorContributor((AnchorContributor) getFigure());
            // else: give up, the lifeline is deleted from the diagram
        }
        contributingTo.clear();
    }

    // TODO Pull up to superclass
    @SuppressWarnings("unchecked")
    protected void refreshVisualsBounds() {
        final GraphNode model = getGraphNode();
        final EOFigure figure = (EOFigure) getFigure();
        final Rectangle bounds = new Rectangle(model.getPosition(), model.getSize());

        final Rectangle coveredBounds;
        OccurrenceSpecification o = getUmlElement().getStart();
        if (getUmlElement().getStart() == null) {
        	// The start OccurenceSpecification could not be found
        	// the element is probably deleted 
        	// (ExecutionSpecification deletion deletes Start and Finish
        	return;
        }
        final List coveredList = getCovereds(getUmlElement().getStart()); 
        // TODO Is it correct to use getStart() ?
        Lifeline covered = (coveredList.isEmpty() ? null : (Lifeline) coveredList.get(0));
        if (covered != null) {
            GraphNode coveredView =
                    ModelUtil.getGraphNode(covered, diagram);
            
            if (coveredView == null) {
            	// The covered Lifeline GraphNode is not yet created
            	// ... abort refreshVisualsBounds
            	return;
            }
            coveredBounds = new Rectangle(coveredView.getPosition(), coveredView.getSize());
        } else
            coveredBounds = null;

        final Rectangle preferred = figure.getPreferredFigureBounds(bounds, coveredBounds);

        if (getParent() != null) // may be null: when the EP is removed
            ((GraphicalEditPart) getParent()).setLayoutConstraint(this, figure, preferred);
        figure.setBounds(preferred);
    }

    @SuppressWarnings("unchecked")
    private AnchoringFigure getLifelineFigure(final Lifeline lifeline) {
    	
        final Map<Object, EditPart> editPartRegistry =
            (Map<Object, EditPart>) getViewer().getEditPartRegistry();
    
        GraphNode lifelineView = ModelUtil.getGraphNode(lifeline, diagram);
        GraphicalEditPart lifelineEP = (GraphicalEditPart) editPartRegistry.get(lifelineView);
        
        if (lifelineEP != null) {
            return (AnchoringFigure) lifelineEP.getFigure();
        } 
        // else
        return null;
        
        
//        final Map<Object, EditPart> editPartRegistry =
//                (Map<Object, EditPart>) getViewer().getEditPartRegistry();
//        if (((GraphElement) getModel()).getContainer() == null) {
//            // The element is deleted.
//            return null;
//        }
//        
//        if (diagram != null) {
//            GraphNode lifelineView = ModelUtil.getGraphNode(lifeline, diagram);
//            GraphicalEditPart lifelineEP = (GraphicalEditPart) editPartRegistry.get(lifelineView);
//            return (AnchoringFigure) lifelineEP.getFigure();
//        }
//
//        // The lookup above will fail when the EO is deleted, since by the time
//        // this method is called, the EO is already removed from the model and diagram.
//        for (EditPart part : (Collection<EditPart>) editPartRegistry.values()) {
//            if (part instanceof LifelineEditPart) {
//                GraphicalEditPart lifelineEP = (GraphicalEditPart) part;
//                GraphNode lifelineView = (GraphNode) lifelineEP.getModel();
//                if (ModelUtil.<NamedElement> getSemantic(lifelineView) == lifeline)
//                    return (AnchoringFigure) lifelineEP.getFigure();
//            }
//        }
//        return null;
    }
    
    /**
     * Retrieve covered {@link Lifeline}
     * @return the covered {@link Lifeline} or null (when the {@link Lifeline} is deleted)
     */
    private Lifeline getCoveredLifeline() {

      // Try to retrieve the covered Lifeline
      final ExecutionSpecification execSpec = getUmlElement();
      Lifeline lifeline = null;
      
      OccurrenceSpecification start = execSpec.getStart();
      if (start == null) {
    	  Debug.log(new Exception("Start OccurenceSpecification not found."));
    	  return null;
      }
      
      EList<Lifeline> lifelines = start.getCovereds();
      if (lifelines.isEmpty()) {
      	// No covered Lifeline found...
      	return null;
      }
      
      // Only the fist covered Lifeline is considered
      // This might be a problem if more than one Lifeline is covered...
      // ... is this correct considering UML specification ?
      lifeline = lifelines.get(0);

      return lifeline;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void umlModelChanged(Notification evt) {
        switch (evt.getFeatureID(UMLPackage.class)) {
        case UMLPackage.NAMED_ELEMENT:
            // ignore
            break;

        case UMLPackage.EXECUTION_SPECIFICATION__START:
        case UMLPackage.EXECUTION_SPECIFICATION__FINISH:
        case UMLPackage.EXECUTION_SPECIFICATION__COVERED: {
            switch (evt.getEventType()) {
            case Notification.REMOVE:
            case Notification.REMOVE_MANY:
//                deactivate(evt.getOldValue());
//                break;

            case Notification.ADD:
            case Notification.ADD_MANY:
//                activate(evt.getNewValue());
 //               updateBounds(evt.getNewValue());
            }
            break;

            // refreshVisuals();
            // break;
        }

        default:
            super.umlModelChanged(evt);
        }
    }

    // TODO Refactor - The code below is identical in TerminateEP

//    protected void lifelineChanged(Notification evt) {
//        switch (evt.getFeatureID(Di2Package.class)) {
//        case Di2Package.GRAPH_NODE__SIZE:
//        case Di2Package.GRAPH_NODE__POSITION:
//            updateBounds(evt.getNotifier());
//        }
//    }

    @SuppressWarnings("unchecked")
    private void updateBounds(final Object lifeline) {
        final GraphNode lifelineView = getLifelineView(lifeline);

        final GraphNode model = (GraphNode) getModel();
        final Rectangle currentBounds = new Rectangle(model.getPosition(), model.getSize());

        final EOFigure figure = (EOFigure) getFigure();
        final Rectangle preferredBounds =
                figure.getPreferredModelBounds(currentBounds, new Rectangle(lifelineView
                        .getPosition(), lifelineView.getSize()));

        if (!preferredBounds.equals(currentBounds)) {
            model.setSize(preferredBounds.getSize());
            model.setPosition(preferredBounds.getLocation());
        }

        refreshVisuals();
    }

    @SuppressWarnings("unchecked")
    private GraphNode getLifelineView(final Object o) {
        if (o instanceof GraphNode)
            return (GraphNode) o;

        Lifeline lifeline = null;
        if (o instanceof Lifeline)
            lifeline = (Lifeline) o;
        else {
            List list = (List) o;
            if (!list.isEmpty())
                lifeline = (Lifeline) list.get(0);
        }

        GraphNode lifelineView = null;
        if (lifeline != null) {
            GraphElement ge = (GraphElement) getModel();
            if (ge.getContainer() != null) {
                lifelineView = ModelUtil.getGraphNode(lifeline, diagram);
            }
        }

        return lifelineView;
    }

    // --- Activation ---
    @Override
    public void activate() {
		if (this.isActive()) {
			return;
		}
		super.activate();
				
		// Start listening for changes in the model
		((Notifier) this.getModel()).eAdapters().add(graphElementListener);
		
		// Add listener on covered Lifeline GraphNode to detect changes
		Lifeline lifeline = getCoveredLifeline();
		if (lifeline != null) {
			GraphNode lifelineGraphNode = getLifelineView(lifeline);
			if (lifelineGraphNode != null) {
				((Notifier) lifelineGraphNode).eAdapters().add(graphElementListener);
			} else {
				Debug.log(new Exception("GraphNode not found for covered lifeline."));
			}
		} else {
			Debug.log(new Exception("Covered Lifeline not found."));
		}

		Iterator propertiesIterator= getGraphNode().getProperty().iterator();
		while(propertiesIterator.hasNext()){
			((Notifier)propertiesIterator.next()).eAdapters().add(graphElementListener);
		}
    }

    @Override
    public void deactivate() {
		if (!this.isActive()) {
			return;
		}

		// stop listening for changes in the model
		((Notifier) this.getModel()).eAdapters().remove(graphElementListener);
		
		// Remove change listener on covered Lifeline GraphNode if the Lifeline GraphNode still exists
		Lifeline lifeline = contributingTo.iterator().next();
		if (lifeline != null) {
			GraphNode lifelineGraphNode = getLifelineView(lifeline);
			if (lifelineGraphNode != null) {
				((Notifier) lifelineGraphNode).eAdapters().remove(graphElementListener);
			}
		}

		Iterator propertiesIterator= getGraphNode().getProperty().iterator();
		while(propertiesIterator.hasNext()){
			((Notifier) propertiesIterator.next()).eAdapters().remove(graphElementListener);
		}		
		
		super.deactivate();
    }
    
//    // --- Activation ---
//
//    @SuppressWarnings("unchecked")
//    @Override
//    public void activate() {
//        super.activate();
//        activate(getUmlElement().getStart());
//        // attachLifelineAdapter(getUMLModel());
//    }
//
//    @SuppressWarnings("unchecked")
//    private void activate(Object o) {
//        if (o instanceof ExecutionSpecification)
//            o = ((ExecutionSpecification) o).getStart();
//
//        if (o instanceof OccurrenceSpecification) {
//            ((EObject) o).eAdapters().add(umlAdapter);
//            o = ((OccurrenceSpecification) o).getCovereds();
//        }
//
//        if (o instanceof Lifeline || o instanceof List) {
//            final GraphNode lifelineView = getLifelineView(o);
//            if (lifelineView != null)
//                lifelineView.eAdapters().add(lifelineAdapter);
//        }
//    }
//
//    @Override
//    public void deactivate() {
//        removeAllAnchorContributions();
//
//        deactivate(getUmlElement().getStart());
//        // detachLifelineAdapter(getUMLModel());
//        super.deactivate();
//
//    }
//
//    @SuppressWarnings("unchecked")
//    private void deactivate(Object o) {
//        removeAllAnchorContributions();
//
//        if (o instanceof ExecutionSpecification)
//            o = ((ExecutionSpecification) o).getStart();
//
//        if (o instanceof OccurrenceSpecification) {
//            ((EObject) o).eAdapters().remove(umlAdapter);
//            o = ((OccurrenceSpecification) o).getCovereds();
//        }
//
//        if (o instanceof Lifeline || o instanceof List) {
//            final GraphNode lifelineView = getLifelineView(o);
//            if (lifelineView != null)
//                lifelineView.eAdapters().remove(lifelineAdapter);
//        }
//    }

    /*
     * @SuppressWarnings("unchecked") private void attachLifelineAdapter(Object o) { if (o
     * instanceof ExecutionOccurrence) o = ((ExecutionOccurrence)o).getStart(); if (o
     * instanceof EventOccurrence) { ((EObject)o).eAdapters().add(lifelineAdapter); o =
     * ((EventOccurrence)o).getCovereds(); } if (o instanceof Lifeline || o instanceof
     * List) { final GraphicalElement<Lifeline> lifelineView = getLifelineView(o); if
     * (lifelineView != null) lifelineView.eAdapters().add(lifelineAdapter); } }
     * @SuppressWarnings("unchecked") private void detachLifelineAdapter(Object o) { if (o
     * instanceof ExecutionOccurrence) o = ((ExecutionOccurrence)o).getStart(); if (o
     * instanceof EventOccurrence) { ((EObject)o).eAdapters().remove(lifelineAdapter); o =
     * ((EventOccurrence)o).getCovereds(); } if (o instanceof Lifeline || o instanceof
     * List) { final GraphicalElement<Lifeline> lifelineView = getLifelineView(o); if
     * (lifelineView != null) lifelineView.eAdapters().remove(lifelineAdapter); } }
     */

//    protected final AdapterImpl lifelineAdapter = new AdapterImpl() {
//        public boolean isAdapterForType(final Object type) {
//            return type instanceof EObject;
//        }
//
//        public void notifyChanged(final Notification msg) {
//            if (msg.isTouch())
//                return;
//            lifelineChanged(msg);
//        }
//    };
}
